package cn.xiuxius.embedded.command.flash;

import cn.xiuxius.embedded.command.converter.*;
import cn.xiuxius.embedded.config.EasyYamlConfig;
import cn.xiuxius.embedded.model.*;
import lombok.extern.slf4j.Slf4j;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

@Slf4j
@Command(name = "adv", description = "Advanced flash using stcgal with full arguments.", mixinStandardHelpOptions = true)
public class AdvancedFlashCommand implements Callable<Integer> {

    @CommandLine.ParentCommand
    private FlashCommand flashCommand;

    @CommandLine.Mixin
    private FlashCommandOptions options;

    public FlashCommandOptions getOptions() {
        return flashCommand.getOptions();
    }

    // 波特率，默认 stcgal 是 115200
    @Option(names = {"-b", "--baud"}, description = "Transfer baud rate. Default: 115200.", converter = IntegerTypeConverter.class)
    private IntegerFlashOptionType baudRate = IntegerFlashOptionType.DEFAULT;

    // 握手波特率
    @Option(names = {"-l", "--handshake"}, description = "Handshake baud rate. Default: 2400.", converter = IntegerTypeConverter.class)
    private IntegerFlashOptionType handshakeBaud = IntegerFlashOptionType.DEFAULT;

    // RC 振荡器频率，仅适用于 STC15+
    @Option(names = {"-t", "--trim"}, description = "RC oscillator trim frequency in kHz (STC15+ only).", converter = IntegerTypeConverter.class)
    private IntegerFlashOptionType trimFrequency = IntegerFlashOptionType.DEFAULT;

    // 自动复位标志位
    @Option(names = {"-a", "--autoreset"}, description = "Enable autoreset by asserting DTR.", converter = FlagTypeConverter.class)
    private CmdFlagFlashOptionType autoReset = CmdFlagFlashOptionType.DEFAULT;

    // 重置使用的引脚
    @Option(names = {"-A", "--resetpin"}, description = "Pin to hold during autoreset: dtr or rts. Default: dtr.", converter = StringTypeConverter.class)
    private StringFlashOptionType resetPin = StringFlashOptionType.DEFAULT;

    // 自定义电源重启命令
    @Option(names = {"-r", "--resetcmd"}, description = "Shell command for power cycling instead of DTR.", converter = StringTypeConverter.class)
    private StringFlashOptionType resetCommand = StringFlashOptionType.DEFAULT;

    // 启用调试输出
    @Option(names = {"-D", "--debug"}, description = "Enable debug output.", converter = FlagTypeConverter.class)
    private CmdFlagFlashOptionType debug = CmdFlagFlashOptionType.DEFAULT;

    // 仅擦除芯片
    @Option(names = {"-e", "--erase-only"}, description = "Only erase chip, do not write.", converter = FlagTypeConverter.class)
    private CmdFlagFlashOptionType eraseOnly = CmdFlagFlashOptionType.DEFAULT;

    @Option(names = "--eeprom-erase", description = "Use next cycle to erase EEPROM (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType eepromEraseEnabled = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--user-program-eeprom", description = "Flash split between code and EEPROM in 512-byte blocks (IntegerFlashOptionType: 512–65024 or DEFAULT)", converter = EEPROMConvert.class)
    private IntegerFlashOptionType userProgramEeprom = IntegerFlashOptionType.DEFAULT;

    @Option(names = "--por-reset-delay", description = "Power-on reset delay mode (SHORT/LONG/DEFAULT)")
    private PorResetFlashOptionType porResetDelay = PorResetFlashOptionType.DEFAULT;

    @Option(names = "--cpu-core-voltage", description = "CPU core voltage level (LOW/MID/HIGH/DEFAULT)")
    private LMHFlashOptionType cpuCoreVoltage = LMHFlashOptionType.DEFAULT;

    @Option(names = "--watchdog-por", description = "Enable watchdog on power-on reset (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType watchdogPorEnabled = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--watchdog-prescale", description = "Watchdog prescaler (IntegerFlashOptionType: 2,4,8,...,256 or DEFAULT)", converter = IntegerTypeConverter.class)
    private IntegerFlashOptionType watchdogPrescale = IntegerFlashOptionType.DEFAULT;

    @Option(names = "--bsl-pindetect", description = "Enable BSL only when pins are low (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType bslPindetectEnabled = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--reset-pin", description = "If true: GPIO; false: reset pin (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType resetPinEnabled = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--ale", description = "Enable ALE pin (BooleanFlashOptionType: true/false/DEFAULT, only for STC89)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType aleEnabled = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--uart1-remap", description = "Route UART1 to UART2 pins for single-wire UART (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType uart1Remap = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--epwm-open-drain", description = "Use open-drain mode for EPWM pins on reset (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType epwmOpenDrain = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--clock-gain", description = "Enable external crystal clock gain (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType clockGain = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--xram", description = "Enable internal XRAM (BooleanFlashOptionType: true/false/DEFAULT, STC89 only)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType xramEnabled = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--clock-source", description = "Clock source mode (INTERNAL/EXTERNAL/DEFAULT)", converter = ClockSourceConverter.class)
    private ClockSourceFlashOptionType clockSource = ClockSourceFlashOptionType.DEFAULT;

    @Option(names = "--oscillator-delay", description = "Oscillator stable delay (IntegerFlashOptionType: 4096, 8192, ..., 32768 or DEFAULT)", converter = IntegerTypeConverter.class)
    private IntegerFlashOptionType oscillatorStableDelay = IntegerFlashOptionType.DEFAULT;

    @Option(names = "--watchdog-idle-stop", description = "Stop watchdog in idle mode (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType watchdogStopIdle = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--lvd-threshold", description = "Low voltage detection threshold (IntegerFlashOptionType: 0–7 or DEFAULT)", converter = IntegerTypeConverter.class)
    private IntegerFlashOptionType lowVoltageThreshold = IntegerFlashOptionType.DEFAULT;

    @Option(names = "--eeprom-lvd-inhibit", description = "Ignore EEPROM write under low voltage (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType eepromLvdInhibit = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--rstout-por", description = "RSTOUT/RSTSV pin state after POR (LOW/HIGH/DEFAULT)", converter = LHConverter.class)
    private LHFlashOptionType rstoutPorState = LHFlashOptionType.DEFAULT;

    @Option(names = "--uart2-passthrough", description = "Enable passthrough from UART1 to UART2 (BooleanFlashOptionType: true/false/DEFAULT)", converter = BooleanTypeConverter.class)
    private BooleanFlashOptionType uart2Passthrough = BooleanFlashOptionType.DEFAULT;

    @Option(names = "--uart2-pin-mode", description = "UART2 TX pin output mode (PUSH_PULL/NORMAL/DEFAULT)", converter = Uart2PinModeConverter.class)
    private Uart2PinFlashOptionType uart2PinMode = Uart2PinFlashOptionType.DEFAULT;


    private List<FlashOptionWrapper> buildOptions() {
        List<FlashOptionWrapper> options = new ArrayList<>();
        StringFlashOptionType port = StringFlashOptionType.from(flashCommand.getOptions().getPortName());
        STCFlashOptionType chipProtocol = STCFlashOptionType.from(flashCommand.getOptions().getChipProtocol());
        EasyYamlConfig.AdvanceConfig advanceConfig = flashCommand.getConfig().getStcgl().getAdvanceConfig();
        options.add(new FlashOptionWrapper().setName("port").setFlag("-p").setValue(port).setIsDefaultValue(flashOptionWrapper -> false));
        options.add(new FlashOptionWrapper().setName("chipProtocol").setFlag("-P").setValue(chipProtocol));
        options.add(new FlashOptionWrapper().setName("baud").setFlag("-b").setValue(baudRate).setIsDefaultValue(wrapper -> {
            if (baudRate == IntegerFlashOptionType.DEFAULT) {
                return true;
            }
            if (wrapper.getValue() instanceof IntegerFlashOptionType i) {
                return i.getValue() == 115200;
            }
            return true;
        }));
        options.add(new FlashOptionWrapper().setName("handshakeBaud").setFlag("-l").setValue(handshakeBaud).setIsDefaultValue(wrapper -> {
            if (handshakeBaud.isDefault()) return true;
            if (wrapper.getValue() instanceof IntegerFlashOptionType i) {
                return i.getValue() == 2400;
            }
            return true;
        }));
        options.add(new FlashOptionWrapper().setName("trim").setFlag("-t").setValue(handshakeBaud));
        options.add(new FlashOptionWrapper().setName("autoreset").setFlag("-a").setOnlyFlag(true));
        options.add(new FlashOptionWrapper().setName("resetPin").setFlag("-A").setValue(resetPin));
        options.add(new FlashOptionWrapper().setName("resetCommand").setFlag("-r").setValue(resetCommand));
        options.add(new FlashOptionWrapper().setName("debug").setFlag("-D").setOnlyFlag(true));
        options.add(new FlashOptionWrapper().setName("eraseOnly").setFlag("-e").setOnlyFlag(true));
        // Advanced Boolean and IntegerFlashOptionType options
        options.add(new FlashOptionWrapper().setName("eepromEraseEnabled")
                .setFlag("eeprom_erase_enabled")
                .setValue(selector(eepromEraseEnabled, advanceConfig.getEepromEraseEnabled()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("userProgramEeprom")
                .setFlag("program_eeprom_split")
                .setValue(selector(userProgramEeprom, advanceConfig.getUserProgramEeprom()))
                .setAdvanced(true)
                .setIsDefaultValue(option -> {
                    if (userProgramEeprom.isDefault()) return true;
                    if (option.getValue() instanceof IntegerFlashOptionType i) {
                        return i.getValue() == 512;
                    }
                    return true;
                }));

        options.add(new FlashOptionWrapper().setName("porResetDelay")
                .setFlag("por_reset_delay")
                .setValue(selector(porResetDelay, advanceConfig.getPorResetDelay()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("cpuCoreVoltage")
                .setFlag("cpu_core_voltage")
                .setValue(selector(cpuCoreVoltage, advanceConfig.getCpuCoreVoltage()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("watchdogPorEnabled")
                .setFlag("watchdog_por_enabled")
                .setValue(selector(watchdogPorEnabled, advanceConfig.getWatchdogPorEnabled()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("watchdogPrescale")
                .setFlag("watchdog_prescale")
                .setValue(selector(watchdogPrescale, advanceConfig.getWatchdogPrescale()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("bslPindetectEnabled")
                .setFlag("bsl_pindetect_enabled")
                .setValue(selector(bslPindetectEnabled, advanceConfig.getBslPindetectEnabled()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("resetPinEnabled")
                .setFlag("reset_pin_enabled")
                .setValue(selector(resetPinEnabled, advanceConfig.getResetPinEnabled()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("aleEnabled")
                .setFlag("ale_enabled")
                .setValue(selector(aleEnabled, advanceConfig.getAleEnabled()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("uart1Remap")
                .setFlag("uart1_remap")
                .setValue(selector(uart1Remap, advanceConfig.getUart1Remap()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("epwmOpenDrain")
                .setFlag("epwm_open_drain")
                .setValue(selector(epwmOpenDrain, advanceConfig.getEpwmOpenDrain()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("clockGain")
                .setFlag("clock_gain")
                .setValue(selector(clockGain, advanceConfig.getClockGain()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("xramEnabled")
                .setFlag("xram_enabled")
                .setValue(selector(xramEnabled, advanceConfig.getXramEnabled()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("clockSource")
                .setFlag("clock_source")
                .setValue(selector(clockSource, advanceConfig.getClockSource()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("oscillatorDelay")
                .setFlag("oscillator_stable_delay")
                .setValue(selector(oscillatorStableDelay, advanceConfig.getOscillatorStableDelay()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("watchdogStopIdle")
                .setFlag("watchdog_stop_idle")
                .setValue(selector(watchdogStopIdle, advanceConfig.getWatchdogStopIdle()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("lvdThreshold")
                .setFlag("low_voltage_threshold")
                .setValue(selector(lowVoltageThreshold, advanceConfig.getLowVoltageThreshold()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("eepromLvdInhibit")
                .setFlag("eeprom_lvd_inhibit")
                .setValue(selector(eepromLvdInhibit, advanceConfig.getEepromLvdInhibit()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("rstoutPor")
                .setFlag("rstout_por_state")
                .setValue(selector(rstoutPorState, advanceConfig.getRstoutPorState()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("uart2Passthrough")
                .setFlag("uart2_passthrough")
                .setValue(selector(uart2Passthrough, advanceConfig.getUart2Passthrough()))
                .setAdvanced(true));

        options.add(new FlashOptionWrapper().setName("uart2PinMode")
                .setFlag("uart2_pin_mode")
                .setValue(selector(uart2PinMode, advanceConfig.getUart2PinMode()))
                .setAdvanced(true));

        return options;

    }


    private FlashOptionType<?> selector(FlashOptionType<?> option, FlashOptionType<?> config) {
        //如果都是DEFAULT 就返回DEFAULT
        //如果option不是default 则返回 option
        if (!option.isDefault()) {
            return option;
        }
        return config;
    }


    @Override
    public Integer call() {
        if (flashCommand == null) {
            CommandLine.usage(this, System.out);
            return 1;
        }
        if (flashCommand.prepare() != 0) {
            return 1;
        }
        File stcgal = flashCommand.getStcgal();
        List<FlashOptionWrapper> flashOptionWrappers = buildOptions();
        boolean b = doAdvFlush(stcgal, flashOptionWrappers, flashCommand.getHexFile());
        if (!b) {
            log.error("Flash failed.");
            return 1;
        }
        return 0;
    }


    private boolean doAdvFlush(File stcgal, List<FlashOptionWrapper> options, File codeImage) {
        log.info("------------------ Advanced Flash Start ------------------");
        List<String> command = new ArrayList<>();
        command.add(stcgal.getAbsolutePath());
        command.addAll(FlashOptionWrapper.toCommand(options));
        command.add(flashCommand.getHexFile().getAbsolutePath());
        log.info("Executing command: {}", String.join(" ", command));
        ProcessBuilder pb = new ProcessBuilder(command);
        pb.redirectErrorStream(true);
        Process process = null;
        try {
            process = pb.start();
            log.info("⏳♻️ Waiting for device reset...");
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
                String line;
                while ((line = reader.readLine()) != null) {

                    log.info("[stcgal-adv] {}", line);
                }
            }
            int exitCode = process.waitFor();
            if (exitCode != 0) {
                log.error("❌ Flash failed with exit code: {}", exitCode);
                return false;
            } else {
                log.info("✅ Flash succeeded.");
                log.info("----------------------- Download & Flush Target End -------------------------------");
                return true;
            }
        } catch (Exception e) {
            log.error("❌ Flash process failed.", e);
            return false;
        } finally {
            if (process != null) {
                process.destroyForcibly();
            }
        }
    }
}
